package org.zebra.mybatis.plus.ext.audit.impl;

import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.JdbcParameter;
import net.sf.jsqlparser.expression.RowConstructor;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectBody;
import net.sf.jsqlparser.statement.select.SetOperationList;
import net.sf.jsqlparser.statement.values.ValuesStatement;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.ParameterMapping;
import org.zebra.mybatis.plus.ext.audit.DataAudit;
import org.zebra.mybatis.plus.ext.audit.OperationResult;
import org.zebra.mybatis.plus.ext.redundancy.util.TableColumnNameUtil;

import java.util.*;

/**
 * @author zhanghongbin
 */
public class InsertDataAudit extends DataAudit {

    private Map<String, Set<String>> ignoredTableColumns;
    private Insert insertStmt;
    private BoundSql boundSql;
    private TableInfo tableInfo;

    public InsertDataAudit(
            Map<String, Set<String>> ignoredTableColumns, Insert insertStmt, BoundSql boundSql, TableInfo tableInfo) {
        this.ignoredTableColumns = ignoredTableColumns;
        this.insertStmt = insertStmt;
        this.boundSql = boundSql;
        this.tableInfo = tableInfo;
    }

    public OperationResult process() {
        OperationResult result = new OperationResult();
        result.setOperation("insert");
        result.setTableName(insertStmt.getTable().getFullyQualifiedName());
        result.setRecordStatus(true);
        Map<String, Object> updatedColumnDatas = this.getUpdatedColumnData(result.getTableName(), boundSql);
        result.buildDataStr(this.compareAndGetUpdatedColumnDatas(
                ignoredTableColumns, result.getTableName(), new OriginalDataObj(), updatedColumnDatas));
        return result;
    }

    private Map<String, Object> getUpdatedColumnData(String tableName, BoundSql updateSql) {
        Map<String, Object> columnNameValMap =
                new HashMap<>(updateSql.getParameterMappings().size());
        Map<Integer, String> columnSetIndexMap =
                new HashMap<>(updateSql.getParameterMappings().size());
        List<Column> selectItemsFromUpdateSql = new ArrayList<>();
        List<Column> columnList;
        // 处理手动编写sql无法获取列名问题
        if (insertStmt.getColumns() == null) {
            columnList = new ArrayList<>();
            for (ParameterMapping parameterMapping : updateSql.getParameterMappings()) {
                String[] columnArray = parameterMapping.getProperty().split("\\.");
                String columnName;
                if (columnArray.length == 2) {
                    columnName = columnArray[1];
                } else {
                    columnName = columnArray[0];
                }
                columnList.add(new Column(TableColumnNameUtil.smartConvert(true, columnName)));
            }
        } else {
            columnList = insertStmt.getColumns();
        }
        selectItemsFromUpdateSql.addAll(columnList);
        columnNameValMap.putAll(detectInsertColumnValuesNonJdbcParameters());
        return this.getColumn(this.tableInfo, updateSql, columnNameValMap, columnSetIndexMap, selectItemsFromUpdateSql);
    }

    private Map<String, String> detectInsertColumnValuesNonJdbcParameters() {
        Map<String, String> columnNameValMap = new HashMap<>(4);
        final Select select = insertStmt.getSelect();
        List<Column> columns = insertStmt.getColumns();
        if (select != null && select.getSelectBody() instanceof SetOperationList) {
            SetOperationList setOperationList = (SetOperationList) select.getSelectBody();
            final List<SelectBody> selects = setOperationList.getSelects();
            if (CollectionUtils.isEmpty(selects)) {
                return columnNameValMap;
            }
            final SelectBody selectBody = selects.get(0);
            if (!(selectBody instanceof ValuesStatement)) {
                return columnNameValMap;
            }
            ValuesStatement valuesStatement = (ValuesStatement) selectBody;
            if (valuesStatement.getExpressions() instanceof ExpressionList) {
                ExpressionList expressionList = (ExpressionList) valuesStatement.getExpressions();
                List<Expression> expressions = expressionList.getExpressions();
                for (Expression expression : expressions) {
                    if (expression instanceof RowConstructor) {
                        final ExpressionList exprList = ((RowConstructor) expression).getExprList();
                        if (exprList != null) {
                            final List<Expression> insertExpList = exprList.getExpressions();
                            for (int i = 0; i < insertExpList.size(); ++i) {
                                Expression e = insertExpList.get(i);
                                if (!(e instanceof JdbcParameter)) {
                                    final String columnName = columns.get(i).getColumnName();
                                    final String val = e.toString();
                                    columnNameValMap.put(columnName, val);
                                }
                            }
                        }
                    }
                }
            }
        }
        return columnNameValMap;
    }
}
